(**Contient toutes les fonctions qui peuvent être appelées par tout le monde, joueurs y compris
On trouve notament les fonctions d'affichages, de tests standards, de personnalités et les fonctions de vote.
Le type joueur est défini ici, tous les joueurs doivent hériter de cette classe pour pouvoir jouer et être gérés par le conteur*)

(**Quantité d'éléments affichés par le programme
{ol list}
- 0 correspond à un affichage total et n'est recommandé que pour la recherche d'erreur.
- 1 correspond à l'affichage de tous les affichages proposés par l'arbitre.
- 2 correspond à une utilisation standard pour observer une partie et ses dessous.
- 3 correspond à un affichage selon un point de vue d'un joueur standard.
- 4 correspond à un affichage minimal pour vérifier qu'une partie se déroule bien (avec avertissements)
- 5 n'affiche rien sauf messages d'erreurs. (sans avertissements)

Cette liste est à jour dans la page du wiki UsingExecutable
*)
let verbose=ref 3;;

(**Initialisation de l'aléatoire*)
let seed = int_of_float (Unix.time ()) ;;

Random.init (seed);;


(**Fonctions d'affichage*)

(**Affichage d'une chaîne standard*)
let v_print_string level str=if !verbose<= level then print_string str;;
(**Affichage des chaînes acceptant la syntaxe des Printf.printf c'est à dire avec paramètres*)
let v_print level=if !verbose<= level then Printf.printf else Printf.ifprintf stdout;;

(**Affichage d'une chaîne dans un beau cadre *)
(*TODO mais assez problématique*)

(**Affichage d'un tableau d'entier, puis passage à la ligne*)
let print_int_array level tab = 
if !verbose <= level
then (Array.iter (fun x->Printf.printf "%i " x) tab;
print_newline () );;

(**Affichage d'un tableau de booléens _unused_ (?) *)
let print_bool_array level tab=
    if !verbose <= level then
    (for i=0 to Array.length tab -1 do if tab.(i) then print_string "true " else print_string "false " done;
    print_string "\n";
    flush stdout)
    ;;

(**Autres fonctions inutiles*)

(**Teste s'il existe un élément du tableau vérifiant un prédicat*)
(*Attention le prédicat porte sur l'élément du tableau*)
let array_exists predicat tab=
    let i=ref 0 in
    while !i<Array.length tab && not (predicat tab.(!i)) do incr i done;
    not (!i=Array.length tab)
;;

(**Teste si tout les éléments d'un tableau vérifient un prédicat*)
(*Attention le prédicat porte sur l'élément du tableau*)
let array_all predicat tab= not (array_exists (fun x->not (predicat x)) tab);;

(**Teste s'il existe un élément du tableau vérifiant un prédicat*)
(*Attention le prédicat porte sur l'indice du tableau*)
let array_exists_2 predicat tab=
    let i=ref 0 in
    while !i<Array.length tab && not (predicat !i) do incr i done;
    not (!i=Array.length tab)
;;
(**Teste si tout les éléments d'un tableau vérifient un prédicat*)
(*Attention le prédicat porte sur l'indice du tableau*)
let array_all_2 predicat tab= not (array_exists_2 (fun x->not (predicat x)) tab);;

(**Trouve l'indice correspondant au max au sens d'une certaine relation*)
let array_max_i tab (<)=
    let n=Array.length tab in
    let i_best=ref 0 in
    for i=0 to n-1 do
        if tab.(!i_best) < tab.(i) then i_best := i
        done;
    !i_best
    ;;


(**----------------------------------------------------------------*)
(** Début des définitions *)

v_print 4 "Arbitre: L'initialisation al\130atoire est %i\n" seed;;
(*Ainsi si un problème apparait, on peut récréer exactement les mêmes conditions pour vérifier si on l'a bien corrigé, il suffit d'imposer la seed à la valeur problématique*)

(**Définition du type des personnalités des joueurs*)
type perso = Unknown |Loup | Villageois| Voyante|Cupidon|Chasseur | Sorciere|Mort of perso| Amoureux of perso;;

(**Définition de la structure d'une information échangée de la forme id_information * information*)
type information=int*(int array);;

(**Conversion d'un type en la personnalité correspondante et réciproquement*)
(** *)
let int2perso n=
    match n with |0->Unknown |1->Loup|2->Villageois|3->Voyante |4->Sorciere|5->Cupidon|6->Chasseur |_->assert false
;;
(**Conversion d'une perso en entier*)
let rec perso2int pers=
(**De cette conversion dépend l'ordre de l'idi8, attention à bien mettre à jour à chaque modif !*)
    match pers with 
        |Unknown -> 0 |Loup->1|Villageois->2 |Voyante->3|Sorciere->4 | Cupidon -> 5 | Chasseur->6
        |Mort sthing ->(v_print_string 4 "vous avez demand\130 l'identification perso2int d'un mort attention, (issue19 ?)\n";perso2int sthing)
        |Amoureux sthing->perso2int sthing
;;

(**Conversion d'une personnalité en la chaîne associée*)
let rec perso2string pers=
    match pers with 
    |Unknown -> "Unknown" 
    | Loup ->"Loup"|Villageois->"Villageois"
    |Voyante->"Voyante"
    |Sorciere->"Sorciere"
    |Cupidon -> "Cupidon"
    |Chasseur -> "Chasseur"
    |Mort persbis-> (perso2string persbis)^" (Mort)" 
    | Amoureux persbis -> (perso2string persbis)^" (Amoureux)"
;;

(**Affichage d'un tableau de personnalités*)
let print_perso_array tab=
    Array.iter (fun x->( v_print 2 "%s " (perso2string x)) ) tab;print_newline ()
;;


(**La fonction c_is nécessite un type particulier qui ne doit pas être utilisé ailleurs à cause de son ambiguité*)
type perso_caution = C of perso|CMort| CAmoureux;;
(**Fonction indiquant si, selon le conteur, la personne est de telle perso*)
let is (cperso:perso_caution) (pers:perso)=
    match cperso with
    |CAmoureux -> (match pers with Amoureux _ -> true | Mort Amoureux _ -> true | _ -> false (*attention à l'identification des morts*))
    |CMort -> (match pers with Mort _->true |Amoureux Mort _ -> failwith "j'ai nomm\130 amoureux un mort ce qui est contraire \133 l'odre normal des \130venements !" |_->false)
    |C Loup -> (match pers with | Loup -> true | Amoureux Loup -> true | _ -> false)
    |C Villageois -> (match pers with | Villageois -> true | Amoureux Villageois -> true | _ -> false)
    |C Voyante -> (match pers with | Voyante -> true | Amoureux Voyante -> true | _ -> false)
    |C Cupidon ->(match pers with | Cupidon -> true | Amoureux Cupidon -> true | _ -> false)
    |C Sorciere -> (match pers with | Sorciere -> true | Amoureux Sorciere -> true | _ -> false)
    |C Chasseur -> (match pers with | Chasseur | Amoureux Chasseur | Mort Chasseur-> true | _ -> false)
    |C (Amoureux sth) ->pers = Amoureux sth
    |C (Mort sth) -> pers= Mort sth
    |C Unknown -> pers = Unknown
;;


(**Classe des joueurs: dans chaque module, le joueur définit sa sous classe avec sa manière propre de répondre aux questions et d'assimiler les informations 
ici sont définies les éléments essentiels utilisés par le conteur
*)

class virtual joueur c_nbjoueurs numjoueur=
  object (self)
    (** Chaque joueur connaît le nombre de participants à la partie*)
    val nbjoueurs = c_nbjoueurs
    (** Un joueur est identitifié par son id*)
    val id = numjoueur
    (** Cette valeur contient le nom de la sous-classe du joueur*)
    val virtual classe : string
    (**Les deux fonctions les plus importantes d'un joueur sont définies :*)
    (**La méthode pour assimiler les informations données par le conteur*)
    method virtual donne_info : information -> unit
    (**La méthode pour répondre aux questions du conteur*)
    method virtual pose_question : information -> information
    (**enfin, des méthodes pour répondre aux questions très standards du conteur, indépendante de la classe du joueur, ce qui empêche certaines formes de triche*)
    (** donner sa sous-classe*)
    method get_classe = classe
    (**donner son identifiant*)
    method get_id = (id:int)
    (** donner le nombre de joueurs dans la partie (utile suelement pour le joueur lui-même)*)
    method get_nbjoueurs = (nbjoueurs:int)
  end;;
 
(**Définition des fonctions qui ne sont utiles qu'au conteur*)

(**Donne la répartition des LG dans lors de la distribution des rôles*)
let carte_LG nb_joueurs =
    (* je sais cette  manière d'écrire est contraire à l'idée CaMLique du match...with mais c'est plus lisible*)
    match nb_joueurs with
    |_ when nb_joueurs < 12 -> (>) (5+1+1) 
    |_ when nb_joueurs < 17 -> (>) (6+1+1)
    |_ when nb_joueurs < 22 -> (>) (7+1+1)
    |_ -> (>) (2+(nb_joueurs-2)/5)

(*A propos de la fonction carte_LG: si on veut rajouter un type de joueurs dont on veut qu'il y ait admettons 1 joueur supplémentaire dans une partie
il faut rajouter +1 à chaque fin de ligne
*)
    
(**Renvoie un tableau pour un ordre aléatoire des joueurs afin de pouvoir leur donner une personnalité au hasard*)
let generer_ordre_aleatoire nb_joueurs =
    let ordre = Array.init nb_joueurs (fun i->i) in
    let permut i1 i2= (let tmp=ordre.(i1) in ordre.(i1) <- ordre.(i2); ordre.(i2) <- tmp) in
    for i=0 to nb_joueurs* 2 do 
         permut (Random.int nb_joueurs) (Random.int nb_joueurs) (*Permutations, on l'espère, aléatoires pour mélanger les joueurs*)
        done;
    ordre
;;

(** Attribue aléatoirement une personnalité à chacun des joueurs*)
let repartition nb_joueurs=
    if nb_joueurs < Regles.nb_joueurs_min
        then failwith (Printf.sprintf "Le nombre de joueurs est insuffisant pour attribuer correctement les rôles") (*Règle n°6*);
    let rep= generer_ordre_aleatoire nb_joueurs in
    (*on a désormais un tableau aléatoire avec les id des joueurs*)
    let rep2=Array.make nb_joueurs Villageois in
    for i=0 to nb_joueurs-1 do 
        if carte_LG nb_joueurs i then rep2.(i)<- Loup else () (*si le joueur pioche les premières cartes du paquet, il est LG !*)
        done;
    rep2.(1)<- Voyante;
    rep2.(2)<- Sorciere;
    rep2.(3)<- Cupidon;
    rep2.(4)<- Chasseur;
    let rep3=Array.make nb_joueurs Unknown in
    for i=0 to nb_joueurs-1 do rep3.(rep.(i))<-rep2.(i) done;
    rep3
;;

(**Calcule le nombre de joueurs interprétant chaque perso d'après le tableau de répartition des persos*)
let idi8 repartition=
    (**Cette fonction dépend de l'ordre donné par perso2int*)
    let idi8_tab= Array.make (1+1+1+1+1+1+1) 0 in
    for id=1 to Array.length repartition -1 do
        let id_perso= perso2int repartition.(id) in
        idi8_tab.(id_perso) <- idi8_tab.(id_perso)+1
        done;
    idi8_tab.(0) <- 2* idi8_tab.(perso2int Cupidon);
    idi8_tab
;;

(** Détermine si le vote se conclue par une majorité absolue et de toute façon le joueur plébiscité*)
let vote_majorite (resultats:int array)=
    let imaxl=ref [0] and sum=ref 0 in (*on stocke la liste des joueurs les plus plébiscités, qui comporte eventuellement des joueurs à égalité*)
    for i=1 to Array.length resultats-1 do
        sum:=!sum+ resultats.(i); (*on stocke le nombre de votes au total*)
        match compare resultats.(List.hd !imaxl) resultats.(i) with
            |0-> imaxl := (i::(!imaxl))
            |(-1)->imaxl:=[i]
            |_-> ()
        done;
    let tmp=Array.of_list !imaxl in let n=Array.length tmp in 
    let choix=tmp.(Random.int n) (*si egalite, le hasard decide [règle n°2 ] +  correction issue8 *) in
    (choix, resultats.(choix) > (!sum)/2) (*joueur plébiscité, majorité absolue*)
;;

(**Transmet aux votants les résultats du vote*)
let communication_du_vote (condition_de_vote: (int ->bool)) c_nbjoueurs joueurs info_a_transmettre=
for id=0 to c_nbjoueurs-1 do if condition_de_vote id then joueurs.(id)#donne_info info_a_transmettre done
;;

(**Fonction gèrant l'intégralité d'un vote*)
let appel_au_vote (condition_de_vote: (int ->bool)) (vote_invalide:information->bool) c_nbjoueurs joueurs idq id_vote type_vote=
    let urne = Array.make c_nbjoueurs 0 and tour=ref 1 and majorite_absolue = ref false and victime=ref (-1) and nb_votants = ref 0 in
    (*on informe que le vote commence*)
    for id=0 to c_nbjoueurs -1 do if condition_de_vote id then joueurs.(id)#donne_info (6,[|0|]) done; 
    while !tour <= 2 && (not !majorite_absolue) do
    (*majorite absolue au 1er tour ou relative au second    [ règle n°1] *)
        (*remise a zéro des votes après un eventuel premier tour*)
        for id=0 to c_nbjoueurs-1 do urne.(id)<- 0 done; 
        (* Vote des joueurs*)
        for id=0 to c_nbjoueurs-1 do
            if condition_de_vote id then
                begin
                incr nb_votants;
                (*Reccueil du vote du joueur*)
                let reponse=ref (joueurs.(id)#pose_question (idq,[|!tour|])) and nbessais=ref 1 in
                (*Vérification de la validité du vote*)
                while vote_invalide !reponse && !nbessais < Regles.nb_vote_max do (*correction issue6: vote contre un mort*)
                    ( v_print 2 "Arbitre: %i vote de façon invalide (contre %i), il n'a plus que %i essais avant de voter contre lui même\n" id ((snd !reponse).(0)) (Regles.nb_vote_max- !nbessais));
                    reponse := joueurs.(id)#pose_question (idq,[|!tour|]);
                    incr nbessais
                    done;
                    
                (* Application de la règle n°3 : vote contre soi-même*)
                if !nbessais = Regles.nb_vote_max
                    then (urne.(id)<-urne.(id)+1;( v_print 3 "Arbitre: %i vote contre lui-même car il a d\130passé la barre des %i votes incorrects\n" id Regles.nb_vote_max))
                    else
                        (*Prise en compte du vote*)
                        begin
                        urne.((snd !reponse).(0))<- urne.((snd !reponse).(0)) + 1 ;
                        ( v_print 2 "Arbitre: %i vote contre %i\n" id (snd !reponse).(0));
                        (*Communication du vote à tous les joueurs concernés*)
                        communication_du_vote (condition_de_vote) c_nbjoueurs joueurs (4,[|id_vote;type_vote;!tour; id;(snd !reponse).(0) |]) 
                        end
                end
            done;

        (*Désignation de la victime*)
        let (vict,maj) = vote_majorite urne in 
        majorite_absolue :=maj ; 
        victime:=vict;
        ( v_print 3 "Conteur: majorit\130: %b, tour: %i\n" !majorite_absolue !tour);
        (*on informe que le tour est fini*)
        for id=0 to c_nbjoueurs -1 do if condition_de_vote id then joueurs.(id)#donne_info (6,[|!tour|]) done;
        incr tour
        done;
    
    (*on informe que le vote est fini*)
    for id=0 to c_nbjoueurs -1 do if condition_de_vote id then joueurs.(id)#donne_info (6,[|3|]) done;

    (*Renvoi du résultat*)
    ((!victime, !nb_votants):int*int)
;;

let rec stack_push_sans_doublon id pile=
(**Insère sans doublon un élément dans une pile \n ceci se fait en placant en O(n) l'élément en bas de la pile*)
    if Stack.is_empty pile 
        then Stack.push id pile
        else
            let premier = Stack.pop pile in
            if premier = id 
            then Stack.push premier pile
            else (stack_push_sans_doublon id pile; Stack.push premier pile)
;;


let rec stack_push_sans_doublon2 ((cause_deces, id_mort) as cadavre) pile=
(**Insère sans doublon un élément dans une pile \n ceci se fait en placant en O(n) l'élément en bas de la pile*)
    if Stack.is_empty pile 
        then Stack.push cadavre pile
        else
            let (cause1,mort1) as premier= Stack.pop pile in
            if mort1 = id_mort 
            then Stack.push premier pile
            else (stack_push_sans_doublon2 cadavre pile; Stack.push premier pile)
;;

let rec stack_filter predicat morgue=
(* ne garde que les cadavre verifiant un certain predicat, __attention__ modification in place*)
    if Stack.is_empty morgue 
    then ()
    else
        let cadavre = Stack.pop morgue in
        if predicat cadavre then (ignore (stack_filter predicat morgue); Stack.push cadavre morgue) else stack_filter predicat morgue
;;